相對於之前做的 Controller-View,LiveView 會一直有一個 process 在維護頁面的狀態,有狀態代表有一個週期循環。
與 Controller-View 一樣,LiveView 也是由 router 出發,當相對應的網址被匹配到時,就會執行對應的 LiveView。
如我們上一篇的
live "/demo", DemoLive
就是當網址是 /demo
時,執行 DemoLive
這個 LiveView module。
當執行 LiveView module 時,會先呼叫 module 裡面的 mount 函式,mount 函式是一個 callback,要由我們主動定義,這個 callback 函式的預期回覆是 {:ok, socket}
。
如上一篇的
def mount(_params, _session, socket) do
{:ok, assign(socket, message: "第一個 LiveView 頁面!", card: "🂠")}
end
通常我們會在在這邊把之後需要的起始值 assign 到 socket 裡面 (使用 assign
)
(雖然很少這麼做但是這個 mount callback 是可以忽略的,等同於不更改 socket 的內容直接回傳原本的 socket {:ok, socket}
)
有時候需要處理網址上的 query 參數,都會在這邊處理,如果不需要可以忽略。
例如目前網頁的頁數:
# 網址: "/notes?page=3"
def handle_params(params, _uri, socket) do
page = Map.get(params, "page", 1)
socket = assign(socket, notes: list_notes_by_page(page))
{:noreply, socket}
end
或是搜尋的條件:
# 網址: "/notes?search=elixir"
def handle_params(%{search: search} = params, _uri, socket) do
socket = assign(socket, notes: list_notes_by_title(search))
{:noreply, socket}
end
在這邊我們把先前組出來的 assigns 鑲嵌到 html template 裡面,並回傳給前端。
def render(assigns) do
~H"""
<h1><%= @title %></h1>
"""
end
在瀏覽器收到的當下,這個網頁都還只是靜態的,直到瀏覽器開始執行 LiveView 內建的 javascript 便開始要求伺服器使用 websocket 來同步頁面。
收到 LiveView 的 Websocket 要求時,Phoenix 會再度執行這些步驟並產生同一個頁面。
第二次的得到畫面之後,會與第一次的畫面做比較,並把不同的部分傳給前端,前端再把這些差異的部分更新到畫面上。
從這邊開始,畫面都由伺服器端的 assigns 狀態來決定。如果畫面需要更改,只需要修改在各個動作回傳的 socket assigns,每一次 assigns 改變時,LiveView 都會自動檢查,並把差異的部分透過 websocket 傳給前端。
例如上一篇的抽卡按鈕
我們在 <.button phx-click="random_card">抽一張牌</.button>
裡面的 phx-click 在觸發時會發送 LiveView 到伺服器,伺服器會執行 handle_event
這個 callback,並回傳新的 assigns 給前端。
def handle_event("random_card", _params, socket) do
{:noreply, assign(socket, card: new_card())}
end
或者是,如果有從伺服器開始的事件,例如我想要在 5 秒後自動抽卡,我們可以在 mount
時使用 send_after
來設定一個計時計。
def mount(_params, _session, socket) do
send_after(self(), :auto_draw, 5000) # 加這行
{:ok, assign(socket, message: "第一個 LiveView 頁面!", card: "🂠")}
end
這樣子在五秒後,就會發送一個 :auto_draw
事件給自己(self()
回傳目前這個 LiveView process 的 pid)
增加一個 handle_info
裡面處理這個 process 事件
def handle_info(:auto_draw, socket) do
{:noreply, assign(socket, card: new_card())}
end
這樣一來,五秒之後就會自動觸發 :auto_draw
事件,並更新畫面。